package org.voidness.squaretilesframework.sprites;

import java.util.Vector;

import org.voidness.oje2d.GLColor;
import org.voidness.oje2d.GLFont;
import org.voidness.oje2d.GLTimedText;
import org.voidness.oje2d.Utils;
import org.voidness.oje2d.Vector2D;
import org.voidness.squaretilesframework.Map;

/** Represents a game actor */
public abstract class GLSprite {
    /** The map to interpret */
    protected Map map = null;

    /** The direction where the camera is moving to */
    private int movingDirection;

    /** The X offset in pixels used to move the camera per-pixel */
    private int offsetX;

    /** The Y offset in pixels used to move the camera per-pixel */
    private int offsetY;

    /** The last time we updated the offsets */
    private long lastTime;

    /** The current time */
    private long currentTime;

    /** This sprite's speed (in tiles per second) */
    protected float speed;

    /** The X component of this sprite's position in the map */
    protected int tileX;

    /** The Y component of this sprite's position in the map */
    protected int tileY;

    /** The animations for the sprite */
    protected Vector<GLHotSpotAnimation> animationsList = null;

    /** The current animation's index */
    protected int currentAnimation;

    /** The text when the sprite "speaks" */
    protected GLTimedText chatText = null;

    /** The player information associated with the sprite */
    // private PlayerInfo playerInfo = null;
    /** The font used when drawing text */
    private GLFont font = null;

    /**
     * Default constructor
     * 
     * @param mMap The map the sprite is in
     * @param mSpeed The speed of the sprite
     * @param mPlayerInfo The associated player information
     */
    public GLSprite(Map mMap, float mSpeed/* , PlayerInfo mPlayerInfo */) {
        map = mMap;
        // setTile(new Vector2D(mPlayerInfo.getTileX(),
        // mPlayerInfo.getTileY()));
        speed = 1000 / (map.getTileSize() * mSpeed); // Speed is in tiles per
                                                     // second
        animationsList = new Vector<GLHotSpotAnimation>();
        currentAnimation = -1;
        currentTime = lastTime = Utils.getTime();
        // playerInfo = mPlayerInfo;
    }

    /**
     * (Re)defines the tile the sprite is in
     * 
     * @param mTile The tile to set
     */
    public void setTile(Vector2D mTile) {
        tileX = mTile.getX();
        tileY = mTile.getY();

        map.setTileOccupier(tileX, tileY, this);
    }

    /**
     * Returns the tile the sprite is in
     * 
     * @return A vector with the position
     */
    public Vector2D getTile() {
        return new Vector2D(tileX, tileY);
    }

    /**
     * Returns the player information
     * 
     * @return The sprite's PlayerInfo
     */
    /*
     * public PlayerInfo getPlayerInfo() { return playerInfo; }
     */

    /**
     * Adds a new animation to the sprite
     * 
     * @param mNewAnimation The new animation to add
     */
    public void addAnimation(GLHotSpotAnimation mNewAnimation) {
        animationsList.add(mNewAnimation);
        currentAnimation = animationsList.indexOf(mNewAnimation);
    }

    /**
     * Loads and adds a new animation to the sprite
     * 
     * @param mFilename The relative path of the animation to load
     */
    public void addAnimation(String mFilename) {
        animationsList.add((GLHotSpotAnimation) Utils.deserialize(mFilename));
        currentAnimation = animationsList.size() - 1;
    }

    /**
     * Draws the current animation using full alpha
     * 
     * @param mX The X component of the sprite's position
     * @param mY The Y component of the sprite's position
     */
    public void draw(int mX, int mY) {
        draw(mX, mY, 1.0f);
    }

    /**
     * Draws the current animation
     * 
     * @param mX The X component of the sprite's position
     * @param mY The Y component of the sprite's position
     * @param mAlpha The alpha value to use
     */
    public void draw(int mX, int mY, float mAlpha) {
        if (currentAnimation > -1 && currentAnimation < animationsList.size()) {
            GLHotSpotAnimation animation =
                    (GLHotSpotAnimation) animationsList
                            .elementAt(currentAnimation);
            animation.draw(mX - offsetX, mY - offsetY, mAlpha);
        }
    }

    /**
     * Draws the text, if the sprite is talking
     * 
     * @param mX The X component of the place we want the text
     * @param mY The Y component of the place we want the text
     */
    public void drawText(int mX, int mY) {
        if (chatText != null) {
            if (chatText.isDead())
                chatText = null;
            else {
                // Draw half the text size backwards (relative to sprite's
                // hotspot)
                chatText.draw(mX
                        - (int) ((chatText.getText().length() * 0.9f * 16) / 2)
                        - offsetX, mY - 100 - offsetY, 0.9f, GLColor.WHITE);
            }
        }
    }

    /**
     * Draws the sprite's name
     * 
     * @param mX The X component of the place we want the name
     * @param mY The Y component of the place we want the name
     */
    public void drawName(int mX, int mY) {
        /*
         * if (playerInfo.getName() != null && font != null) {
         * font.print(playerInfo.getName(), mX - (int)
         * ((playerInfo.getName().length() 0.9f 16) / 2) - offsetX + 1, mY + 20
         * - offsetY + 1, 0.9f, GLColor.BLACK); font.print(playerInfo.getName(),
         * mX - (int) ((playerInfo.getName().length() 0.9f 16) / 2) - offsetX,
         * mY + 20 - offsetY, 0.9f, GLColor.GREEN); }
         */
    }

    /**
     * (Re)defines the font to use when drawing text
     * 
     * @param mFont The font to use
     */
    public void setChatFont(GLFont mFont) {
        font = mFont;
    }

    /**
     * Define the talking text of the sprite
     * 
     * @param mText The text to say
     * @param mTimeToLive The time the text will remain on screen
     */
    public void say(String mText, long mTimeToLive) {
        chatText = new GLTimedText(mText, font, mTimeToLive);
    }

    /**
     * Returns whether the sprite is talking
     * 
     * @return True if it is, false otherwise
     */
    public boolean isTalking() {
        return (chatText != null);
    }

    /**
     * (Re)defines the current animation
     * 
     * @param mIndex The index of the animation to set
     */
    public void setCurrentAnimation(int mIndex) {
        if (mIndex > -1 && mIndex < animationsList.size()) {
            currentAnimation = mIndex;
            GLHotSpotAnimation animation =
                    (GLHotSpotAnimation) animationsList
                            .elementAt(currentAnimation);
            animation.reset();
        }
    }

    /**
     * Returns the sprite's screen offset
     * 
     * @return The X component of the offset
     */
    public int getOffsetX() {
        return offsetX;
    }

    /**
     * Returns the sprite's screen offset
     * 
     * @return The Y component of the offset
     */
    public int getOffsetY() {
        return offsetY;
    }

    /**
     * Returns the sprite's map position
     * 
     * @return The X component of the position
     */
    public int getTileX() {
        return tileX;
    }

    /**
     * Returns the sprite's map position
     * 
     * @return The Y component of the position
     */
    public int getTileY() {
        return tileY;
    }

    /**
     * Returns the current animation's index
     * 
     * @return The current index
     */
    public int getCurrentAnimation() {
        return currentAnimation;
    }

    /**
     * Moves the sprite in the map in a limited fashion, i.e., it will respect
     * the map's constraints
     * 
     * @param mDirection The direction where we want him to move
     */
    public void move(int mDirection) {
        move(mDirection, true);
    }

    /**
     * Moves the sprite in the map
     * 
     * @param mDirection The direction where to move
     * @param mLimited Whether the movement is limited or not
     */
    public void move(int mDirection, boolean mLimited) {
        // Let's finish the movement before starting another one
        if (isMoving())
            return;

        // Check what the destination tile is
        int objectiveX = tileX;
        int objectiveY = tileY;
        if (mDirection == Map.NORTH || mDirection == Map.NORTHWEST
                || mDirection == Map.NORTHEAST)
            objectiveY = tileY - 1;
        if (mDirection == Map.SOUTH || mDirection == Map.SOUTHWEST
                || mDirection == Map.SOUTHEAST)
            objectiveY = tileY + 1;
        if (mDirection == Map.WEST || mDirection == Map.NORTHWEST
                || mDirection == Map.SOUTHWEST)
            objectiveX = tileX - 1;
        if (mDirection == Map.EAST || mDirection == Map.NORTHEAST
                || mDirection == Map.SOUTHEAST)
            objectiveX = tileX + 1;

        // Valid movement code is in server now

        // Update the sprite's position in the map
        map.tileAt(tileX, tileY).clearOccupation();
        map.tileAt(objectiveX, objectiveY).setOccupier(this);

        // Get the map moving
        movingDirection = mDirection;

        switch (movingDirection) {
        case Map.NORTHWEST: {
            // If both work, move northwest
            if (tileY - 1 >= 0 && tileX - 1 >= 0) {
                tileX--;
                tileY--;
                offsetX = -map.getTileSize();
                offsetY = -map.getTileSize();
            }
            // If west is the only one that doesn't work, move north
            else if (tileY - 1 >= 0) {
                movingDirection = Map.NONE;
                move(Map.NORTH);
            }
            // If north doesn't work and west does, move west
            else if (tileX - 1 >= 0) {
                movingDirection = Map.NONE;
                move(Map.WEST);
            }
            // In the northwest corner, can't really move
            else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;

        case Map.NORTHEAST: {
            // If both work, move northwest
            if (tileY - 1 >= 0 && tileX + 1 < map.getWidth()) {
                tileX++;
                tileY--;
                offsetX = map.getTileSize();
                offsetY = -map.getTileSize();
            }
            // If east is the only one that doesn't work, move north
            else if (tileY - 1 >= 0) {
                movingDirection = Map.NONE;
                move(Map.NORTH);
            }
            // If north doesn't work and east does, move east
            else if (tileX + 1 < map.getWidth()) {
                movingDirection = Map.NONE;
                move(Map.EAST);
            }
            // In the northeast corner, can't really move
            else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;

        case Map.SOUTHWEST: {
            // If both work, move southwest
            if (tileY + 1 < map.getHeight() && tileX - 1 >= 0) {
                tileX--;
                tileY++;
                offsetX = -map.getTileSize();
                offsetY = map.getTileSize();
            }
            // If west is the only one that doesn't work, move south
            else if (tileY + 1 < map.getHeight()) {
                movingDirection = Map.NONE;
                move(Map.SOUTH);
            }
            // If south doesn't work and west does, move west
            else if (tileX - 1 >= 0) {
                movingDirection = Map.NONE;
                move(Map.WEST);
            }
            // In the southwest corner, can't really move
            else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;

        case Map.SOUTHEAST: {
            // If both work, move southeast
            if (tileY + 1 < map.getHeight() && tileX + 1 < map.getWidth()) {
                tileX++;
                tileY++;
                offsetX = map.getTileSize();
                offsetY = map.getTileSize();
            }
            // If east is the only one that doesn't work, move south
            else if (tileY + 1 < map.getHeight()) {
                movingDirection = Map.NONE;
                move(Map.SOUTH);
            }
            // If south doesn't work and east does, move east
            else if (tileX + 1 < map.getWidth()) {
                movingDirection = Map.NONE;
                move(Map.EAST);
            }
            // In the southeast corner, can't really move
            else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;

        case Map.NORTH: {
            if (tileY - 1 >= 0) {
                tileY--;
                offsetY = -map.getTileSize();
            } else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }

        }
            break;

        case Map.SOUTH: {
            if (tileY + 1 < map.getHeight()) {
                tileY++;
                offsetY = map.getTileSize();
            } else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;

        case Map.EAST: {
            if (tileX + 1 < map.getWidth()) {
                tileX++;
                offsetX = map.getTileSize();
            } else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;

        case Map.WEST: {
            if (tileX - 1 >= 0) {
                tileX--;
                offsetX = -map.getTileSize();
            } else {
                offsetX = 0;
                offsetY = 0;
                movingDirection = Map.NONE;
            }
        }
            break;
        }

        /*
         * // Previous movement code if (tileX + dI >= 0 && tileX + dI <
         * map.getWidth() && tileY + dJ >= 0 && tileY + dJ < map.getHeight()) {
         * tileX += dI; tileY += dJ; }
         */
    }

    /**
     * Ask whether this camera is moving or not.
     * 
     * @return True if is moving, false if not
     */
    public boolean isMoving() {
        return (movingDirection != Map.NONE);
    }

    /**
     * The basic idea is to decrement the offset value until it reaches 0,
     * meaning that we finished moving smoothly across tiles and are now in the
     * new tile. Actually, we are in the tile from the moment we ask the map to
     * move, but this will probably be changed in the near future. Also, as a
     * remark, this is only currently set for perfect squared tiles, as we set
     * both offsets. If they are not perfect squares, it will keep decrementing
     * the first to reach zero and it will go to negative values until the other
     * reaches zero as well. This will cause the map not to be drawn accurately.
     */
    public void update() {
        // Lets move one tile per second
        if ((currentTime = Utils.getTime()) - lastTime > speed) {
            switch (movingDirection) {
            case Map.NORTHWEST: {
                offsetX += map.getTileSize() / 10;
                offsetY += map.getTileSize() / 10;
                if (offsetX >= 0 && offsetY >= 0) // Warning, only square tiles,
                                                  // no rectangles or i guess it
                                                  // doesn't work
                {
                    offsetX = 0;
                    offsetY = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.NORTHEAST: {
                offsetX -= map.getTileSize() / 10;
                offsetY += map.getTileSize() / 10;
                if (offsetX <= 0 && offsetY >= 0) // Warning, only square tiles,
                                                  // no rectangles or i guess it
                                                  // doesn't work
                {
                    offsetX = 0;
                    offsetY = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.SOUTHWEST: {
                offsetX += map.getTileSize() / 10;
                offsetY -= map.getTileSize() / 10;
                if (offsetX >= 0 && offsetY <= 0) // Warning, only square tiles,
                                                  // no rectangles or i guess it
                                                  // doesn't work
                {
                    offsetX = 0;
                    offsetY = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.SOUTHEAST: {
                offsetX -= map.getTileSize() / 10;
                offsetY -= map.getTileSize() / 10;
                if (offsetX <= 0 && offsetY <= 0) // Warning, only square tiles,
                                                  // no rectangles or i guess it
                                                  // doesn't work
                {
                    offsetX = 0;
                    offsetY = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.NORTH: {
                offsetY += map.getTileSize() / 10;
                if (offsetY >= 0) {
                    offsetY = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.SOUTH: {
                offsetY -= map.getTileSize() / 10;
                if (offsetY <= 0) {
                    offsetY = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.EAST: {
                offsetX -= map.getTileSize() / 10;
                if (offsetX <= 0) {
                    offsetX = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;

            case Map.WEST: {
                offsetX += map.getTileSize() / 10;
                if (offsetX >= 0) {
                    offsetX = 0;
                    movingDirection = Map.NONE;
                }
            }
                break;
            }

            lastTime = currentTime;
        }
    }

    /**
     * Returns the sprite's animations
     * 
     * @return A vector with the sprite's animations
     */
    public Vector<GLHotSpotAnimation> getAnimations() {
        return animationsList;
    }
}